package com.pinfly.common.util;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A class which allows one to easily obtain environment variables using the
 * ${ENV_NAME} notation. Copied from Logging.util.PathUtil.
 */
public class EnvUtil
{
    private static final Pattern s_variableRegex = Pattern.compile ("\\$\\{(.+?)\\}");
    private static final Pattern s_convertBackslashRegex = Pattern.compile ("\\\\");
    private static final Pattern s_singleDollarRegex = Pattern.compile ("\\$");

    /**
     * Resolves any environment variables that are present in the provided path.
     * Environment variables are identified using the following syntax:
     * ${varname} Any backslashes in the values of environment variables will be
     * converted to forward slashes.
     * 
     * @param path The path to replace variables in.
     * @return the path with variables replaced or null if the input path was
     *         null. If a variable is specified in the input path, but not found
     *         in the environment, it will be left unchanged in the resulting
     *         path.
     */
    public static String resolveVariables (String path)
    {
        if (path == null || path.length () == 0)
        {
            return null;
        }
        ResolveResult r = resolveVariablesTrackUnresolved (path);
        return r.getPath ();
    }

    /**
     * Resolves any environment variables that are present in the provided path.
     * Environment variables are identified using the following syntax:
     * ${varname}. Any backslashes in the values of environment variables will
     * be converted to forward slashes. The method returns a ResolveResult
     * instance which allows one to determine what variables (if any) were in
     * the provided path, but could not be resolved.
     * 
     * @param path The path to replace variables in.
     * @return a result object to allow determining whether the variables in
     *         path resolve and replace; or null if the path is null.
     */
    public static ResolveResult resolveVariablesTrackUnresolved (String path)
    {
        if (path == null || path.length () == 0)
        {
            return null;
        }

        if (!path.contains ("${"))
        {
            // no variables
            return new ResolveResult (path, null);
        }

        StringBuffer buff = new StringBuffer ();
        Matcher matcher = s_variableRegex.matcher (path);
        Collection <String> unresolved = new HashSet <String> (5);
        while (matcher.find ())
        {
            String var = matcher.group ();
            String varName = matcher.group (1);
            String env = System.getenv (varName);
            if (env != null)
            {
                String x = s_convertBackslashRegex.matcher (env).replaceAll ("/");

                // it may have a single dollar in environment variable so to
                // replace as \$
                String y = s_singleDollarRegex.matcher (x).replaceAll ("\\" + "$");
                matcher.appendReplacement (buff, y);
            }
            else
            {
                // No environment variable, leave the variable in place.
                // need to protect the leading $ in the variable
                matcher.appendReplacement (buff, "\\" + var);

                unresolved.add (varName);
            }
        }
        matcher.appendTail (buff);
        return new ResolveResult (buff.toString (), unresolved);
    }

    /**
     * Contains the results of substituting environment variables in a provided
     * path.
     */
    public static class ResolveResult
    {
        private final String m_path;
        private final Collection <String> m_unresolved;

        private ResolveResult (String val, Collection <String> unresolvedVars)
        {
            m_path = val;
            if (unresolvedVars == null)
            {
                m_unresolved = Collections.EMPTY_SET;
            }
            else
            {
                m_unresolved = Collections.unmodifiableCollection (unresolvedVars);
            }
        }

        /**
         * To retrieve the path with variables replaced
         * 
         * @return the path with variables replaced or null if the input path
         *         was null. If a variable is specified in the input path, but
         *         not found in the environment, it will be left unchanged in
         *         the resulting path.
         */
        public String getPath ()
        {
            return m_path;
        }

        /**
         * If this method returns true, it means there was at least one
         * unresolved variable in the input path.
         * 
         * @return true - if there were any variables that could not be
         *         resolved; false - otherwise.
         */
        public boolean hasUnresolvedVariables ()
        {
            return !m_unresolved.isEmpty ();
        }

        /**
         * Get a collection of unresolved variables
         * 
         * @return a collection of unresolved variables or a empty set if no
         *         unresolved variable.
         */
        public Collection <String> getUnresolvedVariables ()
        {
            return m_unresolved;
        }

        @Override
        public String toString ()
        {
            StringBuilder buff = new StringBuilder ();
            buff.append ("path=").append (m_path).append (", unresolvedVars=").append (m_unresolved);
            return buff.toString ();
        }

    }

}
